package com.region.http.client.core;

import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.adapter.logging.LoggerAdapterFactory;
import com.region.http.client.model.RequestMeta;
import com.region.http.client.protector.RequestProtector;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 26225
 * @date 2024/9/1 21:28
 * @description
 */
public final class ProtectorMechanism implements AutoCloseable {

    private final LoggerAdapter loggerAdapter = LoggerAdapterFactory.getLogger(getClass());

    private final Map<Method, AtomicBoolean> protectorStatusData = new HashMap<>();

    private final Map<Method, Double> averageData = new ConcurrentHashMap<>();

    private ScheduledExecutorService scheduledExecutorService;

    public ProtectorMechanism() {
        if (!Registers.getInstance().isProtectorEmpty()) {
            scheduledExecutorService = defaultScheduleExecutor();
        }
    }

    private ScheduledExecutorService defaultScheduleExecutor() {
        return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            AtomicInteger threadNumber = new AtomicInteger(1);
            ThreadGroup group = (System.getSecurityManager() != null) ? System.getSecurityManager().getThreadGroup() :
                    Thread.currentThread().getThreadGroup();

            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(group, r,
                        "Region-Client-Protector-Scheduled-" + threadNumber.getAndIncrement(),
                        0);
                if (t.isDaemon())
                    t.setDaemon(false);
                if (t.getPriority() != Thread.NORM_PRIORITY)
                    t.setPriority(Thread.NORM_PRIORITY);
                return t;
            }
        });
    }

    public void triggerProtector(RequestMeta requestMeta) {
        AtomicBoolean protectorStatus = protectorStatusData.get(requestMeta.getMethod());
        if (protectorStatus == null) {
            protectorStatus = new AtomicBoolean(true);
            protectorStatusData.put(requestMeta.getMethod(), protectorStatus);
        }
        protectorStatus.set(true);
        // reset
        averageData.remove(requestMeta.getMethod());
        AtomicBoolean finalProtectorStatus = protectorStatus;
        if (scheduledExecutorService == null) {
            scheduledExecutorService = defaultScheduleExecutor();
        }
        scheduledExecutorService.schedule(() -> finalProtectorStatus.set(false),
                requestMeta.getProtectorMeta().getIntervalTime(), TimeUnit.SECONDS);
    }

    public boolean isTrigger(RequestMeta requestMeta) {
        AtomicBoolean atomicBoolean = protectorStatusData.get(requestMeta.getMethod());
        return atomicBoolean == null ? false : atomicBoolean.get();
    }

    public Object invokeProtectorMethod(RequestMeta requestMeta, Object[] args) {
        try {
            RequestProtector protector = Registers.getInstance().getProtector(requestMeta.getProtectorMeta().getProtectorClazz());
            Method method = requestMeta.getMethod();
            return method.invoke(protector, args);
        } catch (Exception e) {
            loggerAdapter.error("The method invoke error.", e);
        }
        return null;
    }

    public void saveResponseTime(RequestMeta requestMeta, double time) {
        if (loggerAdapter.isDebugEnabled()) {
            loggerAdapter.debug("The request meta info is " + requestMeta);
            loggerAdapter.debug("The execute time:" + time + "ms");
        }
        Double averageTime = averageData.get(requestMeta.getMethod());
        if (averageTime == null) {
            averageData.put(requestMeta.getMethod(), Double.valueOf(time));
            if (time > requestMeta.getProtectorMeta().getExceedsTime()) {
                triggerProtector(requestMeta);
            }
            return;
        }
        double avg = (averageTime.doubleValue() + time) / 2;
        averageData.put(requestMeta.getMethod(), Double.valueOf(time));
        if (avg > requestMeta.getProtectorMeta().getExceedsTime()) {
            triggerProtector(requestMeta);
        }
        if (loggerAdapter.isDebugEnabled()) {
            loggerAdapter.debug("The request meta info is " + requestMeta);
            loggerAdapter.debug("The execute time:" + time + "ms");
            loggerAdapter.debug("The average time:" + avg + "ms");
        }
    }

    public void close() {
        if (scheduledExecutorService != null && !scheduledExecutorService.isShutdown()) {
            this.scheduledExecutorService.shutdown();
        }
    }
}
